/*
 * Copyright 2020-2025 the original author or authors.
 * You cannot use this file unless authorized by the author.
 */
package org.ipig.commons.helper;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

//import org.apache.http.conn.ssl.SSLContextBuilder;

/**
 * HTTP实用工具类
 *
 * @author <a href="mailto:comchnts@163.com">chinats</a>
 * @version $$Id: HttpHelper.java 21 2020-05-20 03:47:00Z ts <comchnts@163.com> $$
 * @since 1.0
 */
@Slf4j
public class HttpHelper {
    //是整个连接池的最大连接数
    private static int maxTotal = 500;
    //是每个route默认的最大连接数
    private static int defaultMaxPerRoute = 20;
    private static int socketTimeout = 300000;
    private static int connectTimeout = 600000;
    private static int connectionRequestTimeout = 60000;
    private static long idleTimeOut = 300;
    private static final String WEB_HTTP = "http";
    private static final String WEB_HTTPS = "https";
    private static SSLConnectionSocketFactory sslsf = null;
    private static PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = null;
    private static SSLContextBuilder builder = null;
    private static volatile CloseableHttpClient httpClient = null;
    public static final String ENCODING = "UTF-8";
    private static HttpRequestRetryHandler httpRequestRetryHandler = null;
    public static final String APPLICATION_JSON = "application/json";
    public static final String CONTENT_TYPE_TEXT_JSON = "text/json";

    /**
     * GET 请求【默认请求3次】
     *
     * @param url
     * @return
     */
    public static String doGet(String url) {
        return doGet(url, 2);
    }

    /**
     * GET 请求
     * <li>注：【请求次数=1+retryNum次】<li/>
     *
     * @param url
     * @param retryNum
     * @return
     */
    public static String doGet(String url, int retryNum) {
        String result = StringUtils.EMPTY;
        try {
            HttpGet httpGet = new HttpGet(url);
            CloseableHttpResponse response = HttpHelper.getCloseableHttpClient().execute(httpGet);
            try {
                if (response != null) {
                    HttpEntity resEntity = response.getEntity();
                    if (resEntity != null) {
                        result = EntityUtils.toString(resEntity, ENCODING);
                        EntityUtils.consume(resEntity);
                    }
                }
            } finally {
                if (response != null) {
                    response.close();
                }
            }
        } catch (Exception e) {
            if (retryNum <= 0) {
                return result;
            }
            log.error("重试访问【{}】倒数第{}次,异常信息：{}", url, retryNum, e.getMessage());
            result = doGet(url, --retryNum);
        }
        return result;
    }

    /**
     * post请求【默认请求重试3次】
     *
     * @param httpUri
     * @param map
     * @param charset
     * @return
     */
    public static String doPost(String httpUri, Map<String, String> map, String charset) {
        return doPost(httpUri, map, charset, 2);
    }

    /**
     * post请求
     * <li>注：【请求次数=1+retryNum次】<li/>
     *
     * @param httpUri
     * @param map
     * @param retryNum
     * @return
     */
    public static String doPost(String httpUri, Map<String, String> map, int retryNum) {
        return doPost(httpUri, map, ENCODING, retryNum);
    }

    /**
     * post请求
     * <li>注：【默认请求次数=1+retryNum次】<li/>
     *
     * @param httpUri
     * @param map
     * @param charset
     * @param retryNum
     * @return
     */
    public static String doPost(String httpUri, Map<String, String> map, String charset, int retryNum) {
        HttpPost httpPost = null;
        String result = StringUtils.EMPTY;
        try {
            CloseableHttpClient httpClient = HttpHelper.getCloseableHttpClient();
            httpPost = new HttpPost(httpUri);
            List<NameValuePair> list = new ArrayList<NameValuePair>();
            Iterator iterator = map.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, String> elem = (Map.Entry<String, String>) iterator.next();
                list.add(new BasicNameValuePair(elem.getKey(), elem.getValue()));
            }
            if (list.size() > 0) {
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, charset);
                httpPost.setEntity(entity);
            }
            CloseableHttpResponse response = httpClient.execute(httpPost);
            try {
                if (response != null) {
                    HttpEntity resEntity = response.getEntity();
                    if (resEntity != null) {
                        result = EntityUtils.toString(resEntity, charset);
                        EntityUtils.consume(resEntity);
                    }
                }
            } finally {
                if (response != null) {
                    response.close();
                }
            }
        } catch (Exception e) {
            if (retryNum <= 0) {
                return result;
            }
            log.error("重试访问【{}】倒数第{}次,异常信息：{}", httpUri, retryNum, e.getMessage());
            result = doPost(httpUri, map, charset, --retryNum);
        }
        return result;
    }

    /**
     * post请求【默认请求重试3次】
     *
     * @param httpPost
     * @return
     */
    public static String doPost(HttpPost httpPost) {
        return doPost(httpPost, ENCODING, 2);
    }

    /**
     * post请求【默认请求重试3次】
     *
     * @param httpPost
     * @param charset
     * @return
     */
    public static String doPost(HttpPost httpPost, String charset) {
        return doPost(httpPost, charset, 2);
    }

    /**
     * post请求
     * <li>注：【默认请求次数=1+retryNum次】<li/>
     *
     * @param httpPost
     * @param charset
     * @param retryNum
     * @return
     */
    public static String doPost(HttpPost httpPost, String charset, int retryNum) {
        String result = StringUtils.EMPTY;
        try {
            CloseableHttpResponse response = HttpHelper.getCloseableHttpClient().execute(httpPost);
            try {
                if (response != null) {
                    HttpEntity resEntity = response.getEntity();
                    if (resEntity != null) {
                        result = EntityUtils.toString(resEntity, charset);
                        EntityUtils.consume(resEntity);
                    }
                }
            } finally {
                if (response != null) {
                    try {
                        response.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            if (retryNum <= 0) {
                return result;
            }
            log.error("重试访问倒数第{}次,异常信息：{}", retryNum, e.getMessage());
            result = doPost(httpPost, charset, --retryNum);
        }
        return result;
    }

    /**
     * execute请求
     * <ul>注：
     * <li>默认请求次数=3次
     * <li>返回的 CloseableHttpResponse 对象需要手动进行 close;
     * </ul>
     *
     * @param httpRequest HttpUriRequest
     * @return CloseableHttpResponse
     */
    public static CloseableHttpResponse execute(HttpUriRequest httpRequest) {
        return execute(httpRequest, 2);
    }

    /**
     * execute请求
     * <ul>注：
     * <li>默认请求次数=1+retryNum次
     * <li>返回的 CloseableHttpResponse 对象需要手动进行 close;
     * </ul>
     *
     * @param httpRequest HttpUriRequest
     * @param retryNum    int
     * @return CloseableHttpResponse
     */
    public static CloseableHttpResponse execute(HttpUriRequest httpRequest, int retryNum) {
        CloseableHttpResponse result = null;
        try {
            result = HttpHelper.getCloseableHttpClient().execute(httpRequest);
        } catch (Exception e) {
            if (retryNum <= 0) {
                return result;
            }
            log.error("重试访问倒数第{}次,异常信息：{}", retryNum, e.getMessage());
            result = execute(httpRequest, --retryNum);
        }
        return result;
    }


    /**
     * 获取 CloseableHttpClient
     *
     * @return CloseableHttpClient
     */
    public static CloseableHttpClient getCloseableHttpClient() {
        if (httpClient == null) {
            synchronized (poolingHttpClientConnectionManager) {
                if (httpClient == null) {
                    RequestConfig defaultRequestConfig = RequestConfig.custom()
                            .setSocketTimeout(socketTimeout) //socket读写超时时间
                            .setConnectTimeout(connectTimeout)//连接超时时间
                            .setConnectionRequestTimeout(connectionRequestTimeout)//请求超时时间
                            .setRedirectsEnabled(true)//设置是否允许重定向
                            .build();
                    // 创建默认的httpClient实例.
                    httpClient = HttpClients.custom()
                            .setSSLSocketFactory(sslsf)
                            .setConnectionManager(poolingHttpClientConnectionManager)
                            .setConnectionManagerShared(true)
                            .setDefaultRequestConfig(defaultRequestConfig)
//                            .setRetryHandler(httpRequestRetryHandler) //放弃内核重试，采用手动输入重试次数
                            .build();
                }
            }
        }
        return httpClient;
    }

    static {
        try {
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                // 全部信任 不做身份鉴定
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            }).build();
            sslsf = new SSLConnectionSocketFactory(sslContext);
            Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register(WEB_HTTP, new PlainConnectionSocketFactory())
                    .register(WEB_HTTPS, sslsf)
                    .build();
            poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);
            // 最大连接数
            poolingHttpClientConnectionManager.setMaxTotal(maxTotal);
            // 每个路由基础的连接
            poolingHttpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
            //空闲永久连接检查间隔 ,官方推荐使用这个来检查永久链接的可用性，而不推荐每次请求的时候才去检查
            poolingHttpClientConnectionManager.setValidateAfterInactivity(3000);
            Runtime.getRuntime().addShutdownHook(new Thread("HttpHelper-shutdown-hook") {
                @Override
                public void run() {
                    try {
                        log.warn("Connection manager is shutting down");
                        poolingHttpClientConnectionManager.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });

            //请求重试处理器
            httpRequestRetryHandler = new HttpRequestRetryHandler() {
                public boolean retryRequest(IOException ex, int executionCount, HttpContext context) {
                    if (executionCount >= 3) {// 如果已经重试了3次，就放弃
                        return false;
                    } else if (ex instanceof InterruptedIOException // I/O操作被中断，重试
                            || ex instanceof UnknownHostException // 未连接到主机，重试
                            || ex instanceof HttpHostConnectException // http主机连接异常，重试
                            || ex instanceof ConnectTimeoutException // 连接超时，重试
                            || ex instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，重试
                        return true;
                    }
                    HttpClientContext clientContext = HttpClientContext.adapt(context);
                    HttpRequest request = clientContext.getRequest();
                    // 如果请求是幂等的，就再次尝试
                    return !(request instanceof HttpEntityEnclosingRequest);
                }
            };

            //设置清理任务
            ScheduledExecutorService service = new ScheduledThreadPoolExecutor(1);
            service.scheduleWithFixedDelay(new Runnable() {
                public void run() {
                    if (poolingHttpClientConnectionManager != null) {
                        // 关闭失效的连接
                        poolingHttpClientConnectionManager.closeExpiredConnections();
                        // 可选的, 关闭300秒内不活动的连接
                        poolingHttpClientConnectionManager.closeIdleConnections(idleTimeOut, TimeUnit.SECONDS);
                    }
                }
            }, 60, 120, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error(ExceptionUtils.getFullStackTrace(e));
            e.printStackTrace();
        }
    }

    /**
     * 数据为JSON格式的HttpPost
     *
     * @param url
     * @param jsonContent
     * @return
     */
    public static HttpPost getHttpPostToJson(final String url, final String jsonContent) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader(HTTP.CONTENT_TYPE, APPLICATION_JSON);
        StringEntity se = new StringEntity(jsonContent, ENCODING);
        se.setContentType(CONTENT_TYPE_TEXT_JSON);
        se.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, APPLICATION_JSON));
        httpPost.setEntity(se);
        return httpPost;
    }

    /**
     * 执行http
     * <ul>注：
     * <li>【默认请求3次】
     * <li>【默认字符编码UTF-8】
     * </ul>
     *
     * @param httpclient
     * @param httpPost
     * @return
     */
    public static String doHttpPost(CloseableHttpClient httpclient, HttpPost httpPost) {
        return doHttpPost(httpclient, httpPost, ENCODING, 2);
    }

    /**
     * 执行http
     * <ul>注：
     * <li>【请求次数=1+retryNum次】
     * <li>【默认字符编码UTF-8】
     * </ul>
     *
     * @param httpclient
     * @param httpPost
     * @param retryNum
     * @return
     */
    public static String doHttpPost(CloseableHttpClient httpclient, HttpPost httpPost, int retryNum) {
        return doHttpPost(httpclient, httpPost, ENCODING, retryNum);
    }

    /**
     * 执行http
     * <li>注：【默认请求3次】<li/>
     *
     * @param httpclient
     * @param httpPost
     * @param charsetName
     * @return
     */
    public static String doHttpPost(CloseableHttpClient httpclient, HttpPost httpPost, String charsetName) {
        return doHttpPost(httpclient, httpPost, charsetName, 2);
    }

    /**
     * 执行http
     * <li>注：【请求次数=1+retryNum次】<li/>
     *
     * @param httpclient
     * @param httpPost
     * @param charsetName
     * @param retryNum
     * @return
     */
    public static String doHttpPost(CloseableHttpClient httpclient, HttpPost httpPost, String charsetName, int retryNum) {
        String result = StringUtils.EMPTY;
        try {
            CloseableHttpResponse response = httpclient.execute(httpPost);
            try {
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    InputStream instream = entity.getContent();
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(instream, charsetName));
                        StringBuilder sbout = new StringBuilder();
                        String line = null;
                        while ((line = reader.readLine()) != null) {
                            sbout.append(line);
                        }
                        result = sbout.toString();
                    } finally {
                        try {
                            if (instream != null) {
                                instream.close();
                            }
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                }
            } finally {
                if (response != null) {
                    try {
                        response.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            if (retryNum <= 0) {
                return result;
            }
            log.error("重试访问倒数第{}次,异常信息：{}", retryNum, e.getMessage());
            result = doHttpPost(httpclient, httpPost, charsetName, --retryNum);
        }
        return result;
    }
}
